
export class Vector3 {
    public elements: Float32Array;

    constructor (src?: Float32Array) {
        const v = new Float32Array(3);
        if (src) {
            v[0] = src[0];
            v[1] = src[1];
            v[2] = src[2];
        }
        this.elements = v;
    }

    // 归一化
    public normalize () {
        const v = this.elements;
        const c = v[0], d = v[1], e = v[2]; 
        let g = Math.sqrt(c*c+d*d+e*e);
        if(g){
          if(g == 1)
              return this;
         } else {
           v[0] = 0; v[1] = 0; v[2] = 0;
           return this;
         }
         g = 1/g;
         v[0] = c*g; v[1] = d*g; v[2] = e*g;
         return this;
    }
}

export class Vector4 {
    public elements: Float32Array;

    constructor(src?: Float32Array) {
        const v = new Float32Array(4);
        if (src) {
            v[0] = src[0];
            v[1] = src[1];
            v[2] = src[2];
            v[3] = src[3];
        }
        this.elements = v;
    }
}

/**
 * 四维矩阵辅助类
 * 可以生成平移、旋转、绽放矩阵
 */
export class Matrix4 {
    public elements: Float32Array;

    /**
     * 构造函数，不传参数，默认 4 * 4 单位矩阵
     * @param src 原Matrix4 类型对象
     */
    constructor (src?: Matrix4) {
        if (src) {
            const elements = new Float32Array(16);
            const srcElements = src.elements;
            for (let i = 0; i < 16; i++) {
                elements[i] = srcElements[i];
            }
            this.elements = elements;
        } else {
            this.elements = new Float32Array([
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1,
            ]);
        }
    }

    /**
     * 设置为单位阵
     */
    public setIdentity () {
        const e = this.elements;
        e[0] = 1;   e[4] = 0;   e[8]  = 0;   e[12] = 0;
        e[1] = 0;   e[5] = 1;   e[9]  = 0;   e[13] = 0;
        e[2] = 0;   e[6] = 0;   e[10] = 1;   e[14] = 0;
        e[3] = 0;   e[7] = 0;   e[11] = 0;   e[15] = 1;
        return this;
    }

    /**
     * 根据 src 设置矩阵
     * @param src Matrix4 类型的参数
     */
    public set (src: Matrix4) {
        const srcElems = src.elements;
        const elems = this.elements;
        if (srcElems === elems) return;

        for (let i = 0; i < 16; i++) {
            elems[i] = srcElems[i];
        }
        return this;
    }

    /**
     * 右乘
     */
    public multiply (other: Matrix4) {
        var i, e, a, b, ai0, ai1, ai2, ai3;
  
        // Calculate e = a * b
        e = this.elements;
        a = this.elements;
        b = other.elements;
        
        // If e equals b, copy b to temporary matrix.
        if (e === b) {
            b = new Float32Array(16);
            for (i = 0; i < 16; ++i) {
            b[i] = e[i];
            }
        }
        
        for (i = 0; i < 4; i++) {
            ai0=a[i];  ai1=a[i+4];  ai2=a[i+8];  ai3=a[i+12];
            e[i]    = ai0 * b[0]  + ai1 * b[1]  + ai2 * b[2]  + ai3 * b[3];
            e[i+4]  = ai0 * b[4]  + ai1 * b[5]  + ai2 * b[6]  + ai3 * b[7];
            e[i+8]  = ai0 * b[8]  + ai1 * b[9]  + ai2 * b[10] + ai3 * b[11];
            e[i+12] = ai0 * b[12] + ai1 * b[13] + ai2 * b[14] + ai3 * b[15];
        }
        
        return this;
    }
    public concat (other: Matrix4) {
        return this.multiply(other);
    }

    /**
     * 乘 三维向量，返回一个三维向量
     * @param pos Vector3 对象
     */
    public multiplyVector3 (pos: Vector3) {
        const e = this.elements;
        const p = pos.elements;
        const v = new Vector3();
        const result = v.elements;

        result[0] = p[0] * e[0] + p[1] * e[4] + p[2] * e[ 8] + e[11];
        result[1] = p[0] * e[1] + p[1] * e[5] + p[2] * e[ 9] + e[12];
        result[2] = p[0] * e[2] + p[1] * e[6] + p[2] * e[10] + e[13];

        return v;
    }

    /**
     * 乘 四维向量，返回一个四维向量
     * @param pos Vextor4 对象
     */
    public multiplyVector4 (pos: Vector4) {
        const e = this.elements;
        const p = pos.elements;
        const v = new Vector4();
        const result = v.elements;

        result[0] = p[0] * e[0] + p[1] * e[4] + p[2] * e[ 8] + p[3] * e[12];
        result[1] = p[0] * e[1] + p[1] * e[5] + p[2] * e[ 9] + p[3] * e[13];
        result[2] = p[0] * e[2] + p[1] * e[6] + p[2] * e[10] + p[3] * e[14];
        result[3] = p[0] * e[3] + p[1] * e[7] + p[2] * e[11] + p[3] * e[15];

        return v;
    }

    /**
     * 转置
     */
    public transpose () {
        const e = this.elements;
        let t;

        t = e[ 1];  e[ 1] = e[ 4];  e[ 4] = t;
        t = e[ 2];  e[ 2] = e[ 8];  e[ 8] = t;
        t = e[ 3];  e[ 3] = e[12];  e[12] = t;
        t = e[ 6];  e[ 6] = e[ 9];  e[ 9] = t;
        t = e[ 7];  e[ 7] = e[13];  e[13] = t;
        t = e[11];  e[11] = e[14];  e[14] = t;
      
        return this;
    }

    /**
     * 设置缩放矩阵
     * @param x x方向 scale 倍数
     * @param y y方向 scale 倍数
     * @param z z方向 scale 倍数
     */
    public setScale (x: number, y: number, z: number) {
        const e = this.elements;
        e[0] = x;  e[4] = 0;  e[8]  = 0;  e[12] = 0;
        e[1] = 0;  e[5] = y;  e[9]  = 0;  e[13] = 0;
        e[2] = 0;  e[6] = 0;  e[10] = z;  e[14] = 0;
        e[3] = 0;  e[7] = 0;  e[11] = 0;  e[15] = 1;
        return this;
    }

    /**
     * 在原有矩阵基础上进入缩放
     * @param x x方向 scale 倍数
     * @param y y方向 scale 倍数
     * @param z z方向 scale 倍数
     */
    public scale (x: number, y: number, z: number) {
        const e = this.elements;
        e[0] *= x;  e[4] *= y;  e[8]  *= z;
        e[1] *= x;  e[5] *= y;  e[9]  *= z;
        e[2] *= x;  e[6] *= y;  e[10] *= z;
        e[3] *= x;  e[7] *= y;  e[11] *= z;
        return this;
    }

    /**
     * 设置平移矩阵
     * @param x x方向 平移距离
     * @param y y方向 平移距离
     * @param z z方向 平移距离
     */
    public setTranslate (x: number, y: number, z: number) {
        const e = this.elements;
        e[0] = 1;  e[4] = 0;  e[8]  = 0;  e[12] = x;
        e[1] = 0;  e[5] = 1;  e[9]  = 0;  e[13] = y;
        e[2] = 0;  e[6] = 0;  e[10] = 1;  e[14] = z;
        e[3] = 0;  e[7] = 0;  e[11] = 0;  e[15] = 1;
        return this;
    };

    /**
     * 在原来矩阵的基础上，设置平移矩阵
     * @param x x方向 平移距离
     * @param y y方向 平移距离
     * @param z z方向 平移距离
     */
    public translate (x: number, y: number, z: number): Matrix4 {
        const e = this.elements;
        e[12] += e[0] * x + e[4] * y + e[8]  * z;
        e[13] += e[1] * x + e[5] * y + e[9]  * z;
        e[14] += e[2] * x + e[6] * y + e[10] * z;
        e[15] += e[3] * x + e[7] * y + e[11] * z;
        return this;
    };

    /**
     * 
     * @param angle 角度
     * @param x 旋转轴 x 方向分量
     * @param y 旋转轴 y 方向分量
     * @param z 旋转轴 z 方向分量
     */
    public setRotate (angle: number, x: number, y: number, z: number) {
        var e, s, c, len, rlen, nc, xy, yz, zx, xs, ys, zs;
      
        angle = Math.PI * angle / 180;
        e = this.elements;
      
        s = Math.sin(angle);
        c = Math.cos(angle);
      
        if (0 !== x && 0 === y && 0 === z) {
          // Rotation around X axis
          if (x < 0) {
            s = -s;
          }
          e[0] = 1;  e[4] = 0;  e[ 8] = 0;  e[12] = 0;
          e[1] = 0;  e[5] = c;  e[ 9] =-s;  e[13] = 0;
          e[2] = 0;  e[6] = s;  e[10] = c;  e[14] = 0;
          e[3] = 0;  e[7] = 0;  e[11] = 0;  e[15] = 1;
        } else if (0 === x && 0 !== y && 0 === z) {
          // Rotation around Y axis
          if (y < 0) {
            s = -s;
          }
          e[0] = c;  e[4] = 0;  e[ 8] = s;  e[12] = 0;
          e[1] = 0;  e[5] = 1;  e[ 9] = 0;  e[13] = 0;
          e[2] =-s;  e[6] = 0;  e[10] = c;  e[14] = 0;
          e[3] = 0;  e[7] = 0;  e[11] = 0;  e[15] = 1;
        } else if (0 === x && 0 === y && 0 !== z) {
          // Rotation around Z axis
          if (z < 0) {
            s = -s;
          }
          e[0] = c;  e[4] =-s;  e[ 8] = 0;  e[12] = 0;
          e[1] = s;  e[5] = c;  e[ 9] = 0;  e[13] = 0;
          e[2] = 0;  e[6] = 0;  e[10] = 1;  e[14] = 0;
          e[3] = 0;  e[7] = 0;  e[11] = 0;  e[15] = 1;
        } else {
          // Rotation around another axis
          len = Math.sqrt(x*x + y*y + z*z);
          if (len !== 1) {
            rlen = 1 / len;
            x *= rlen;
            y *= rlen;
            z *= rlen;
          }
          nc = 1 - c;
          xy = x * y;
          yz = y * z;
          zx = z * x;
          xs = x * s;
          ys = y * s;
          zs = z * s;
      
          e[ 0] = x*x*nc +  c;
          e[ 1] = xy *nc + zs;
          e[ 2] = zx *nc - ys;
          e[ 3] = 0;
      
          e[ 4] = xy *nc - zs;
          e[ 5] = y*y*nc +  c;
          e[ 6] = yz *nc + xs;
          e[ 7] = 0;
      
          e[ 8] = zx *nc + ys;
          e[ 9] = yz *nc - xs;
          e[10] = z*z*nc +  c;
          e[11] = 0;
      
          e[12] = 0;
          e[13] = 0;
          e[14] = 0;
          e[15] = 1;
        }
      
        return this;
    };

    /**
     * 在原矩阵的基础上，设置旋转
     * @param angle 角度
     * @param x 旋转轴 x 方向分量
     * @param y 旋转轴 y 方向分量
     * @param z 旋转轴 z 方向分量
     */
    public rotate (angle: number, x: number, y: number, z: number) {
        return this.multiply(new Matrix4().setRotate(angle, x, y, z));
    }

    /**
     * 设置视图矩阵
     * @param eyeX 视点 x 坐标
     * @param eyeY 视点 y 坐标
     * @param eyeZ 视点 z 坐标
     * @param centerX 观察点 x 坐标
     * @param centerY 观察点 y 坐标
     * @param centerZ 观察点 z 坐标
     * @param upX 上方向 x 分量
     * @param upY 上方向 y 分量
     * @param upZ 上方向 z 分量
     */
    public setLookAt (eyeX: number, eyeY: number, eyeZ: number, centerX: number, centerY: number, centerZ: number, upX: number, upY: number, upZ: number) {
        let e, fx, fy, fz, rlf, sx, sy, sz, rls, ux, uy, uz;
      
        fx = centerX - eyeX;
        fy = centerY - eyeY;
        fz = centerZ - eyeZ;
      
        // Normalize f.
        rlf = 1 / Math.sqrt(fx*fx + fy*fy + fz*fz);
        fx *= rlf;
        fy *= rlf;
        fz *= rlf;
      
        // Calculate cross product of f and up.
        sx = fy * upZ - fz * upY;
        sy = fz * upX - fx * upZ;
        sz = fx * upY - fy * upX;
      
        // Normalize s.
        rls = 1 / Math.sqrt(sx*sx + sy*sy + sz*sz);
        sx *= rls;
        sy *= rls;
        sz *= rls;
      
        // Calculate cross product of s and f.
        ux = sy * fz - sz * fy;
        uy = sz * fx - sx * fz;
        uz = sx * fy - sy * fx;
      
        // Set to this.
        e = this.elements;
        e[0] = sx;
        e[1] = ux;
        e[2] = -fx;
        e[3] = 0;
      
        e[4] = sy;
        e[5] = uy;
        e[6] = -fy;
        e[7] = 0;
      
        e[8] = sz;
        e[9] = uz;
        e[10] = -fz;
        e[11] = 0;
      
        e[12] = 0;
        e[13] = 0;
        e[14] = 0;
        e[15] = 1;
      
        // Translate.
        return this.translate(-eyeX, -eyeY, -eyeZ);
    };

    /**
     * 在原矩阵的基础上， 设置视图矩阵
     * @param eyeX 视点 x 坐标
     * @param eyeY 视点 y 坐标
     * @param eyeZ 视点 z 坐标
     * @param centerX 观察点 x 坐标
     * @param centerY 观察点 y 坐标
     * @param centerZ 观察点 z 坐标
     * @param upX 上方向 x 分量
     * @param upY 上方向 y 分量
     * @param upZ 上方向 z 分量
     */
    public lookAt (eyeX: number, eyeY: number, eyeZ: number, centerX: number, centerY: number, centerZ: number, upX: number, upY: number, upZ: number) {
        return this.concat(new Matrix4().setLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ));
    };
}